/**
 * @file snake.cpp
 * @author Ichirinko
 * @brief 贪吃蛇链表混响版（
 * @version 1.2
 * @date 2022-01-22
 * 三回啊三回
 * @copyright Copyright (c) 2022
 * 
 */
#include<cstdio>
#include<string.h>
#include<windows.h>
#include<conio.h>
#include<thread>
#include<time.h>
#include<stdlib.h>

 #include <map>
#include "bass.h"
#pragma comment(lib,"bass.lib")
typedef std::map < std::string, HSTREAM > MusicMap; 


volatile char key='a';//蛇头方向键位
volatile char all_key=' ';//全局键位
int the_world=0;//1:暂停 0:活动

const int map_w=40;//地图左右长度（列）
const int map_h=20;//地图上下长度（行）

int speed=300;
int Point=0;//分数来着
int food[2];//食物的坐标
using namespace std;

char mainmap[map_h][map_w];

typedef struct Snake{
    int x;
    int y;
    char statu='a';
    Snake *previous;
    Snake *next;
}Snake;

Snake *head=NULL;
Snake *tail=NULL;


void init();
void move();
void get_key();
void draw(HANDLE hOutBuf, HANDLE hOutput);
void AddList(int x,int y,int pos=-1);
void update();
int death();
void key_filter(char);
void create_food();



/**
 * @brief 初始化
 * 
 */
void init(){
   
    for(int i=0;i<map_w;i++){
        mainmap[0][i]='#';
        mainmap[map_h-1][i]='#';
        
    }
    for(int i=0;i<map_h;i++){
        mainmap[i][0]='#';
        mainmap[i][map_w-1]='#';
        
    }
       
    for(int i=1;i<map_h-1;i++){
        for(int j=1;j<map_w-1;j++){
            mainmap[i][j]=' ';
        }
    }
    system("title Snake ver1.2");
    system("chcp 65001"); 
    system("mode con:cols=40 lines=20");
     
    AddList(map_h/2,map_w/2);//初始化蛇头位置
    create_food();


    int Volume = 75;
    std::string Music_Name;
    if(rand()%2==1){
       Music_Name = "./m1.mp3";
    }
    else{
       Music_Name = "./m2.mp3";
    }
    
    HSTREAM Init = 0;//流句柄
    MusicMap Music;
    BASS_Init(-1,44100,0,0,0);
    BASS_SetConfig(BASS_CONFIG_GVOL_STREAM,Volume * 100);

    //新建一个迭代器
    MusicMap::iterator _findReturn = Music.find(Music_Name);
    Init = BASS_StreamCreateFile(FALSE,Music_Name.c_str(),0,0, BASS_SAMPLE_LOOP);//得到流句柄
    Music.insert(make_pair(Music_Name,Init));//写进列表中
    BASS_ChannelPlay(Init,TRUE);//播放

    
}





/**
 * @brief 按键过滤器
 * 
 * @param key _getch得到的键位
 * @return char 允许方向键aswd、暂停键p，退出键esc,加速键n，减速键m通过
 */
void key_filter(char getkey){
    char key_allow[]={'a','s','d','w','p',0x1b,'n','m'};
    for(int i=0;i<8;i++){
        if(getkey==key_allow[i]){
            if(i<=3){
                if(the_world==0){
                    key=getkey;
                }
                
            }
            else {
                all_key=getkey;
            }
            //return key_allow[i];
        }
    }
    //return key;

}



/**
 * @brief 添加节点功能
 * 
 * @param x 该节点的x坐标
 * @param y 该节点的y坐标
 * @param statu 蛇头朝向
 * @param pos 添加节点的位置，若填-1或不填，则认为向链表末尾添加一个节点      
 *            添加之后，这个位置之后的节点统一后移
 *            
 */
void AddList(int x,int y,int pos){
    if(pos!=-1){
        
        /*Snake *node=CheckNote(head,pos,2);
        //先统一检查注入节点是否存在
        if(node==NULL){
            printf("Add note failed!\nThe note is invaild\n");
        }
        else{
            Snake * temp=(Snake*)malloc(sizeof(Snake));

            if(pos==0){     //如果是插入成为头节点
                temp->next=head;
                //temp->s=a;
                head=temp;
            }

            else{           //在中间插入节点
               // Snake *node_previous=CheckNote(head,pos-1,2);
                temp->next=node;        //先接后面
                //temp->s=a;
                node_previous->next=temp;   //再接前面
            }
        }*/

    }

    else{
        Snake * temp=(Snake*)malloc(sizeof(Snake));

        //temp->s=a;
        temp->x=x;
        temp->y=y;
        //temp->statu=head->statu;
        temp->next=NULL;
        //temp->previous=tail->previous;
        if(head==NULL){
            head=temp;
            head->previous=NULL;
        }

        else{
            tail->next=temp;
            temp->previous=tail;
        }
        tail=temp;
    }
}






/**
 * @brief 控制蛇头的移动，后续节点的更新
 * 
 */
void move(){
    Snake * temp=head;
    
    
    while (temp->next!=NULL)
    {    
         temp=temp->next;
    } 
    while(temp->previous!=NULL){
        temp->x=temp->previous->x;
        temp->y=temp->previous->y;
        temp=temp->previous;
    }


    switch (key){
        case 'a':{
            if(head->statu!='d'){
                head->y--;
                head->statu='a';
            }
            else {
                head->y++;
                key='d';
            }
            //printf("a\n");
            break;
        }
        case 'd':{
            if(head->statu!='a'){
                head->y++;
                head->statu='d';
            }
            else {
                head->y--;
                key='a';
            }
            //printf("d\n");
            break;
        }
        case 'w':{
            if(head->statu!='s'){
                 head->x--; 
                 head->statu='w'; 
            }
            else {
                head->x++;
                key='s';

            }
            //printf("w\n");
            break;
        }
        case 's':{
            if(head->statu!='w'){
                  head->x++;
                  head->statu='s';
            }
            else {
                head->x--;
                key='w';
                }
            //printf("s\n");
            break;
        }

        
        
    }

    
    
    
}


/**
 * @brief 判断蛇死亡
 * 
 * @return 返回1，蛇挂；返回0，蛇活
 */
int death(){
    if(head->x<=0 ||head->x>=map_h-1||head->y<=0||head->y>=map_w-1){
        return 1;
    }
    else {
        Snake *temp=head;
        temp=temp->next;
        while(temp!=NULL){
            
            
            if(temp->x==head->x && temp->y==head->y){
                
                
                //system("pause");
                return 1;
            }
             temp=temp->next;
        }
    }
    return 0;
}


/**
 * @brief 生成食物
 * 
 */
void create_food(){
    if(1){
    int is_success=1;    
        while(1){
            is_success=1;
            srand((unsigned int)time(NULL));
            food[0]=rand()%(map_h-2)+1;
            food[1]=rand()%(map_w-2)+1;
            Snake *temp=head;
            while(temp!=NULL){
                if(food[0]==temp->x and food[1]==temp->y){
                    is_success=0;
                    break;
                }
                temp=temp->next;
            }
            
            if(is_success==1){
                //is_food=1;
                mainmap[food[0]][food[1]]='@';
                break;
            }
            
        }
        
    }
}

/**
 * @brief 获得键盘输入
 * 
 */
void get_key(){
    while(1){

    
    if(_kbhit()){
        //key=key_filter(_getch());
        key_filter(getch());
        switch (all_key){
         
        
        case 0x1b:{
             exit(0);
             break;
                    }

        case 'p':{   //暂停功能
            //char temp_key=head->statu; //保存暂停前的按键状态
            
                the_world=the_world?0:1;
                all_key=' ';
                //key=temp_key;
            

            break;
        }

        case 'n':{  //加速功能
            if(speed<=100){
                speed=100;
            }
            else{
                speed-=10;
            }
            all_key=' ';
            break;
        }

        case 'm':{  //减速功能
            if(speed>=1000){
                speed=1000;
            }
            else{
                speed+=10;
            }
            all_key=' ';
            break;
        }
        
        }
    }
    Sleep(1);
  }
}


/**
 * @brief 每次循环周期时更新状态
 * 
 * @todo judge()
 */
void update(){
    Snake *temp=head;
    while(temp!=NULL){
        mainmap[temp->x][temp->y]=' ';
        temp=temp->next;
    }
     if(the_world==0){

         int pre_tail[2];
         pre_tail[0]=tail->x;
         pre_tail[1]=tail->y;//用来存储尾巴移动前的位置，用于吃食物后生成下一节


         move();//蛇动 
        /*  if(is_food==0){
             create_food();
         }
         else{//吃到了
             if(head->x==food[0] and head->y==food[1]){
                 is_food=0;
                 Add
             }
         } */

         
             
         //吃到了
             if(head->x==food[0] and head->y==food[1]){
                 //is_food=0;
                 AddList(pre_tail[0],pre_tail[1]);
                 create_food();
                 Point+=10;
             }
         
     }
     

     
    temp=head;
    while(temp!=NULL){
        mainmap[temp->x][temp->y]='O';
        temp=temp->next;
    }
}



/**
 * @brief 绘制画面
 * 
 */
void draw(HANDLE hOutBuf, HANDLE hOutput){
    
      
    //system("cls");
    //HideCursor();
    //SetConsoleActiveScreenBuffer(hOutput);
     
    //printf("\nLeft:a  Right:d  Up:w  Down:s  Pause:p  Exit:esc");
    

    COORD coord = { 0,0 };
	DWORD bytes = 0;
	//static bool flag = true;
	
	//可以减少打印地图的时间有效减少闪频
    
	//SetConsoleActiveScreenBuffer(hOutput);
    /*
    for(int i=0;i<map_h;i++){
        for(int j=0;j<map_w;j++){
            printf("%c",map[i][j]);
        }
        printf("\n");
    } 
    */
	static bool flag = true;
	
	//可以减少打印地图的时间有效减少闪频
	if (flag)
	{
		for (int i = 0; i <= map_h; i++)
		{
			coord.Y = i;
			WriteConsoleOutputCharacterA(hOutBuf, mainmap[i], strnlen(mainmap[i],map_w), coord, &bytes);
		}
		//设置新的缓冲区为活动显示缓冲
		SetConsoleActiveScreenBuffer(hOutBuf);
		flag = false;
	}
	else
	{
		for (int i = 0; i <= map_h; i++)
		{
			coord.Y = i;
			WriteConsoleOutputCharacterA(hOutput, mainmap[i], strnlen(mainmap[i],map_w), coord, &bytes);
		}
		//设置新的缓冲区为活动显示缓冲
		SetConsoleActiveScreenBuffer(hOutput);
		flag = true;
	}
		
		
	
}

void sleep(){
    

    //创建新的控制台缓冲区
	HANDLE hOutBuf = CreateConsoleScreenBuffer(
		GENERIC_READ | GENERIC_WRITE,//定义进程可以往缓冲区读写数据
		FILE_SHARE_READ |FILE_SHARE_WRITE,//定义缓冲区可共享写权限
		NULL,
		CONSOLE_TEXTMODE_BUFFER,
		NULL
	);
	HANDLE hOutput = CreateConsoleScreenBuffer(
		GENERIC_READ | GENERIC_WRITE,//定义进程可以往缓冲区读写数据
		FILE_SHARE_READ |FILE_SHARE_WRITE,//定义缓冲区可共享写权限
		NULL,
		CONSOLE_TEXTMODE_BUFFER,
		NULL
	);
    HANDLE hStd=GetStdHandle(STD_OUTPUT_HANDLE);
	//隐藏两个缓冲区的光标
	CONSOLE_CURSOR_INFO cci;
	cci.bVisible = 0;
	cci.dwSize = 1;
	SetConsoleCursorInfo(hOutput, &cci);
	SetConsoleCursorInfo(hOutBuf, &cci);
    SetConsoleCursorInfo(hStd, &cci);


    while(1){
    update();
    if(!death()){
        draw(hOutBuf, hOutput);    
       
        Sleep(speed);
    }
    else{
        SetStdHandle(STD_OUTPUT_HANDLE ,hStd);
        SetConsoleActiveScreenBuffer(hStd);
        system("cls");
        printf("\tGame Over!\n");
        printf("\tpoint=%d\n",Point);
        if(Point<=20){
            printf("      评级:杂鱼级\n");
        }
        else if(Point>20 and Point<=100){
            printf("      评级:水课级\n");
        }
        else if(Point>100 and Point<=200){
            printf("      评级:普通级\n");
        }
        else if(Point>200 and Point<=500){
            printf("      评级:超人级\n");
        }
        else if(Point>500 ){
            printf("      评级:龙王级\n");
        }
        system("pause");
        exit(0);
    }
     }
}


int main(){

        init();
        
        
        thread t(get_key);

        
        thread s(sleep);
    
        
        t.detach();
        s.detach();
    
        while(1){
            
        }
}